class Application {
    static [string]$Dev= "Dev"
    hidden static [Application] $_instance = $null
    hidden static [PSCustomObject]$_configuration
    hidden Application() {}
    [Application] Setup([PSCustomObject]$Options) {
        
        [Application]::_configuration = $Options

        return $this
    }
    static [Application] GetInstance() {
        if (-not [Application]::_instance) {
            [Application]::_instance = [Application]::new()
        }
        return [Application]::_instance
    }
    [PSCustomObject] Configure([string]$Property) {
        return [Application]::_configuration.$Property
    }
    [PSCustomObject] Configure() {
        return [Application]::_configuration
    }
    [void] InitEnv() {
        [Shortcut]::Environment()
    }
    [void] Run() {
        Write-Host "ok"
        $this.InitEnv()
    }
}

class Shell {
    static [void] Node() {
        fnm env --use-on-cd --shell powershell | Out-String | Invoke-Expression
    }
    static [void] Setup() {
        [Shell]::Node()
    }
}

class Shortcut {
    static [bool] Validation([string]$Name) {
        if ([string]::IsNullOrWhiteSpace($Name)) {
            Write-Warning "No ink command entered"
            return $false
        }

        [PSCustomObject]$Commands = [Application]::GetInstance().Configure("Commands")

        if (
            $null -eq $Commands -or
            $null -eq $Commands.$Name
        ) {
            Write-Warning "No command found (Not configured)"
            return $false
        }

        return $true
    }
    static [void] Execute([string]$Name, [bool]$Uac) {
        try {
            if (-not [Shortcut]::Validation($Name)) {
                return
            }

            [PSCustomObject]$Commands = [Application]::GetInstance().Configure("Commands")
            [string]$Command = $Commands.$Name

            if ($Command.StartsWith("#")) {
                $Command = $Command.Substring(1)

                Invoke-Expression $Command
            }
            else {
                $Expression = "Start $($Command)"

                if (-not $Uac) {
                    Invoke-Expression $Expression
                }
                else {
                    # Start-Process powershell -Verb RunAs -ArgumentList "-NoProfile -Command `"Start-Process '$Expression'`""
                    Start-Process powershell -Verb RunAs -ArgumentList $Expression
                }
            }
        }
        catch {
            Write-Error "Error executing command: $_"
        }
    }
    static [void] Download([hashtable]$App) {
        [string]$Name = $App.Name
        [string]$Dest = $App.Location
        [string]$Root = [Application]::GetInstance().Configure("Root")
        [PSCustomObject]$Apps = [Application]::GetInstance().Configure("Apps")
        [scriptblock]$Validation = {
            if (
                $null -eq $Apps -or
                [string]::IsNullOrWhiteSpace($Name) -or
                $null -eq $Apps.$Name
            ) {
                Write-Warning "No input or configuration provided"
                return $false
            }
            return $true
        }
        if (-not (. $Validation)) {
            return
        }
        [scriptblock]$BitsTransfer = {
            param (
                [hashtable]$Params
            )
            # $bitsJob = Start-BitsTransfer -Source $Source -Destination $Destination -DisplayName $DisplayName -Asynchronous
            $BitsJob = Start-BitsTransfer @Params -Asynchronous
            $startTime = Get-Date
    
            while (($BitsJob.JobState -eq "Transferring") -or ($BitsJob.JobState -eq "Connecting")) {
                $Elapsed = (Get-Date) - $startTime
                $Speed = $BitsJob.BytesTransferred / $Elapsed.TotalSeconds
                $remaining = ($BitsJob.BytesTotal - $BitsJob.BytesTransferred) / $Speed -as [int]
                $Progress = ($BitsJob.BytesTransferred / $BitsJob.BytesTotal) * 100
    
                Write-Progress -Activity "Downloading $($Params.DisplayName)" `
                    -Status (
                    "$([math]::Round($Progress,1))% Complete | " +
                    "Speed: $([math]::Round($Speed/1MB,2)) MB/s | " +
                    "Remaining: $([timespan]::FromSeconds($Remaining).ToString('mm\:ss'))"
                ) `
                    -PercentComplete $Progress
                Start-Sleep -Seconds 1
            }
    
            Complete-BitsTransfer -BitsJob $BitsJob
            Write-Host "[OK] Download completed: $($Params.Destination)" -ForegroundColor Green
        }
        [scriptblock]$RunTask = {
            $Path = $Root
            $Url = $Apps.$Name
            try {
                if ([string]::IsNullOrWhiteSpace($Dest)) {
                    $Path = $Path | Join-Path -ChildPath "Downloads"

                    if (-not (Test-Path -Path $Path)) {
                        $null = New-Item -ItemType Directory -Path $Path
                    }
                }
                else {
                    $Path = $Dest -replace "/", "\"
                    
                    if (
                        $Path -eq "." -or
                        $Path -eq ".\"
                    ) {
                        $Path = Get-Location
                    }
                }
                $Params = @{
                    "Source"      = $Url
                    "Destination" = $Path
                    "DisplayName" = 'FileTransfer'
                    "Priority"    = 'High'
                    # "TransferPolicy" = 'Capped'
                }
                . $BitsTransfer -Params $Params
            }
            catch {
                Write-Error "File transfer failed. Please try again: $_"
                return
            }
            if (Test-Path -Path $Path -PathType Leaf) {
                if ($App.Execution -eq "1") {
                    Invoke-Expression $Path
                }
                else {
                    "explorer /select,$Path" | Invoke-Expression
                }
            }
            Write-Host "Saved to current directory: " -NoNewline
            Write-Host $Path -ForegroundColor DarkGray
        }
        . $RunTask
    }
    static [void] Environment() {
        [scriptblock]$Check = {
            $Environments = [Application]::GetInstance().Configure("Environments")
            
            if ($null -eq $Environments) {
                return $false
            }
            $Paths = [System.Collections.ArrayList]::new()
            $Variables = @{}
            $Environments.PSObject.Properties | ForEach-Object {
                [hashtable]$Environment = $_.Value
                foreach ($Key in $Environment.Keys) {
                    if ("Path" -eq $Key) {
                        $Paths.AddRange(
                            [System.Collections.ArrayList]$Environment.$Key
                        )
                        continue
                    }
                    $Variables.$Key = $Environment.$Key
                }
            }
            if ($Variables.Count) {
                $Variables.GetEnumerator() | ForEach-Object {
                    "`$Env:$($_.Key) = `"$($_.Value)`"" | Invoke-Expression
                    "`$Env:$($_.Key) = `"$($_.Value)`"" | Write-Host
                }
            }
            if ($Paths.Count) {
                [string[]]$Paths = $Paths.GetEnumerator() | ForEach-Object {
                    [string]$Path = $_
                    if ($Path.StartsWith('%')) {
                        return [regex]::Replace($Path, '%(.+?)%', {
                                param([System.Text.RegularExpressions.Match]$Match)
                                [string]$Value = $Match.Groups[1].Value
                                if ($null -ne $Variables.$Value) {
                                    return $Variables.$Value
                                }
                                if ([System.Environment]::GetEnvironmentVariable("$Value")) {
                                    return [System.Environment]::GetEnvironmentVariable("$Value")
                                }
                                $Value
                            })
                    }
                    $Path
                }
                $Env:Path = ($Paths -join ";") + ';' + $Env:Path
            }
        }
        . $Check
        [Shell]::Setup()
    }
}
function Send-WebRequest {
    param (
        [string]$Name,
        [string]$Location,
        [string]$Execution
    )
    [Shortcut]::Download(@{
            Name      = $Name
            Location  = $Location
            Execution = $Execution
        })
}
function Set-Shortcut {
    param (
        [string]$Name,
        [string]$Permission
    )
    [Shortcut]::Execute(
        $Name,
        $Permission -eq "1"
    )
}
function InkConsoleEntry {
    # [CmdletBinding()]
    # param (
    #     [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    #     [PSCustomObject]$Options
    # )
    # process {
    #     if (
    #         $null -ne $Options -and
    #         $null -eq $Options.Root
    #     ) {
    #         $Root = $HOME | Join-Path -ChildPath ".ink"
    #         $Options | Add-Member -NotePropertyName "Root" -NotePropertyValue $Root
    #     }
    #     [Application]::GetInstance().Setup($Options).Run()
    # }
    # PSConsoleHostReadLine
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true)]
        [object]$Root,
        [string]$Work = '.ink'
    )    
    if (
        $null -ne $Root -and 
        $Root.GetType() -eq [string] -and
        -not [string]::IsNullOrEmpty($Root)
    ) {
        $Work = [string]$Root | Join-Path -ChildPath $Work
    }
    else {
        $Work = $HOME | Join-Path -ChildPath $Work
    }
    if (-not (Test-Path -Path $Work)) {
        try {
            $null = New-Item -ItemType Directory -Path $Work
        }
        catch {
            throw 'Unable to create working directory'
        }
    }
    Write-Host "Working..."
    Write-Host $Global:Yx3InkOptions
    [PSCustomObject]$Options = $Global:Yx3InkOptions
    if ($null -eq $Options.Root) {
        $Options | Add-Member -NotePropertyName 'Root' -NotePropertyValue $Root
    }
    [Application]::GetInstance().Setup($Options).Run()
}

New-Alias -Name ink -Value Set-Shortcut -Option ReadOnly -Description "Ink Quick Actions"
New-Alias -Name app -Value Send-WebRequest -Option ReadOnly -Description "App Download / Application Download"

$null = $(
    if ($Global:Yx3InkOptions.Auto -eq $true) {
        InkConsoleEntry
    }
)
